{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "0780977f",
   "metadata": {},
   "source": [
    "### 6.3 最优化方法计算投资组合的最佳仓位"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "87997b33",
   "metadata": {},
   "source": [
    "在前面的学习中，我们分别从截面（多因子模型）和时序（择时策略）两个方面学习了如何对各个股票的未来收益率进行预测；在多因子模型中，我们在同一个时间截面上，计算出每个股票在不同因子上的暴露，通过线性/非线性的方法将多个因子合成综合因子作为股票未来收益率强弱的预测值；在择时策略中，我们计算每个股票的时序信号，更进一步地，我们可以将离散的时序信号值连续化，使得不同股票的择时信号不再是0或1，连续信号的差异化使得在截面上不同股票的择时信号能够进行强弱比较。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4bff10cf",
   "metadata": {},
   "source": [
    "细心的读者可能已经发现了，上文多因子模型和时序策略，提到的最终目标，都是在每一个时间截面上，输出股票对未来收益率的预测值，并且在截面上不同股票之间进行强弱比较。这是因为在实盘中，往往有多种限制约束着我们的投资行为。一个最现实的考量就是资金，以A股为例，买卖股票最小的单位为1手，即100股；如果时序模型同时有100支股票产生买入信号1，又或者多因子模型预测值前100支股票具有同样的值；但此时我们可支配的资金只足够交易50支股票，那么如何从100支股票中定量地挑选50支股票呢？所以尽可能地，我们希望在同一时间截面上，不同股票的预测值之间是有差异的。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0c1e5425",
   "metadata": {},
   "source": [
    "无论是作为主观投资者重仓少数证券，还是基于量化模型的策略，都会面临如上的问题，每一个证券买多少。假设我们的限制条件很少，例如只有前文提到的资金限制，而截面上不同股票的预测值差异足够大，我们能够恰如其分地选出目标数量的证券；而如果截面上的预测值差异不够大，例如我们需要在100支相同预测值相近的股票中挑选实际交易的50支股票，量化模型还可以借助组合优化的方法。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ca5b92b2",
   "metadata": {},
   "source": [
    "由此，便引出了本节内容的主题，投资组合优化，即如何分配资金。下文中我们将详细介绍各类优化方法，在此之前，首先我们先对常用的数学符号进行约定，如下表所示。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e769b07d",
   "metadata": {},
   "source": [
    "$$\n",
    "\\begin{array}{cc}N & \\text { 组合内证券数量 } \\\\ \\omega & \\text { 权重向量 }(\\mathbb{N} \\times 1) \\\\ \\omega_{\\mathrm{i}} & \\text { 证券 } \\mathrm{i} \\text { 的权重 } \\\\ \\mu & \\text { 预期收益率向量 }(\\mathbb{N} \\times 1) \\\\ \\sigma & \\text { 证券波动率向量 }(\\mathbb{N} \\times 1) \\\\ \\sigma_{\\mathrm{i}} & \\text { 证券 } \\mathrm{i} \\text { 的波动率 } \\\\ \\sigma_{i j} & \\text { 证券 } \\mathrm{i} \\text { 和证券 } \\mathrm{i} \\text { 之间的协方差 } \\\\ \\Sigma & \\text { 方差协方差矩阵 }(\\mathbb{N} \\times \\mathbb{N}) \\\\ \\sigma_{\\mathrm{p}} & \\text { 组合波动率 } \\\\ \\mu_{\\mathrm{p}} & \\text { 组合收益率 } \\\\ \\lambda & \\text { 风险厌恶系数 } \\\\ \\mathrm{r}_{f} & \\text { 无风险收益率 }\\end{array}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7ad3b89c",
   "metadata": {},
   "source": [
    "#### 6.3.1 等权重"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ad15e08d",
   "metadata": {},
   "source": [
    "那么在没有任何信息或者偏好时，等权重是最简单的办法，即赋予组合中每个证券相同的权重，意味着我们视每个证券具有同等的重要性。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "79355ba8",
   "metadata": {},
   "source": [
    "$$\n",
    "\\omega_{i}=\\frac{1}{N}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "43a49589",
   "metadata": {},
   "source": [
    "值得一提的是，虽然等权重看起来非常简单，不需要复杂的数学求解，但是等权重组合的业绩表现往往是非常抢眼的，因此在研究中常被用来作为比较基准。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1146d6b1",
   "metadata": {},
   "source": [
    "#### 6.3.2 市值加权"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3c639a9a",
   "metadata": {},
   "source": [
    "对于股票组合而言，在没有任何信息或者偏好时，还有另外一种使用非常普遍的组合方法，即市值加权。通常地，小市值的股票收益率的日波动是大于大市值股票的，因为小市值股票和大市值股票能承载的资金量是不同的，令小市值股票涨/跌1%需要的资金可能远小于大市值的股票。而如果我们希望在组合内各个股票平等的分配资金，由于大小市值这种属性，等权重组合可能需要非常频繁调仓。而市值加权，根据定义，对于选出的股票，按照其市值加权，即"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "984b714c",
   "metadata": {},
   "source": [
    "$$\n",
    "\\omega_{i}=C a p_{i} / \\sum_{i} C a p_{i} \\\\\n",
    "C a p_{i} 为股票 i 的市值\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "802bbf1b",
   "metadata": {},
   "source": [
    "市值加权不需要频繁调仓，往往流动性也最强；不过，市值加权会给与高估值股票过多权重，给与低估值股票过少权重，因此结果在一些结构性行情下可能并不占优。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "57fc2785",
   "metadata": {},
   "source": [
    "#### 6.3.3 最小方差组合（Minimum Variance）"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0d51313d",
   "metadata": {},
   "source": [
    "前文两种组合方法，都是在没有任何信息或者偏好时可以使用的。然而，在实际投资过程中，每个投资者都暴露在海量的信息中，同时每个投资者的风险偏好也是不同的。对于风险厌恶的投资者，自然是希望投资者的风险是最小的。由于总体的风险是未知的，在组合优化中，我们常常用历史收益率的方差最为代理变量，追求组合整体的方差最小，数学表达为，"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d897275e",
   "metadata": {},
   "source": [
    "$$\n",
    "\\operatorname{Min} \\ \\sigma_{\\mathrm{p}}=\\omega^{\\prime} \\Sigma \\omega \\\\\n",
    "\\Rightarrow \\ \\omega \\propto \\Sigma ^{-1} \\mathbf{1}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a00fe9fb",
   "metadata": {},
   "source": [
    "#### 6.3.4 最大分散度（Maximum Diversification）"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cd8b87e5",
   "metadata": {},
   "source": [
    "从组合的方差-协方差矩阵我们可知，组合的整体风险一部分来源于各个证券自身的方差，另一部分来源于证券之间的协方差。因此，如果我们想降低组合风险，就应该尽量分散投资。在2008年，Choueifaty和Coignard于是提出了最大分散度优化，该方法的数学表达为，"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a9dd3692",
   "metadata": {},
   "source": [
    "$$\n",
    "\\operatorname{Max} \\quad D(w)=\\frac{\\omega^{\\prime} \\sigma}{\\sqrt{\\omega^{\\prime} \\Sigma \\omega}} \\\\\n",
    "\\Rightarrow \\ \\omega \\propto \\Sigma^{-1} \\sigma\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7e28eb46",
   "metadata": {},
   "source": [
    "目标函数被称为分散比率，分母为组合波动率，分子为成分的波动率加权平均。该方法最大化资产线性加权波动率与投资组合波动率的比值，故称为最大分散化资产配置组合。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d6c9763e",
   "metadata": {},
   "source": [
    "#### 6.3.5 风险平价（Risk Parity)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8c248a9a",
   "metadata": {},
   "source": [
    "风险平价从风险的角度进行均衡配置，以追求所有证券对组合的风险贡献相同。首先定义所谓的边际风险贡献，即每增加1单位证券$i$的权重$ \\omega_{i} $所引起的组合整体风险的变化，"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6d22286d",
   "metadata": {},
   "source": [
    "$$\n",
    "\\begin{aligned} M R C_{i}=\\frac{\\partial \\sigma_{p}}{\\partial \\omega_{i}} & =\\frac{\\omega_{i} \\sigma_{i}^{2}+\\sum_{j \\neq i} \\omega_{j} \\rho_{i j} \\sigma_{i} \\sigma_{j}}{\\sigma_{p}} \\\\ & =\\frac{\\sum_{j=1}^{N} \\omega_{j} \\rho_{i j} \\sigma_{i} \\sigma_{j}}{\\sigma_{p}} \\\\ & =\\frac{\\rho_{i p} \\sigma_{i} \\sigma_{p}}{\\sigma_{p}} \\\\ & =\\left(\\frac{\\rho_{i p} \\sigma_{i}}{\\sigma_{p}}\\right) \\sigma_{p} \\\\ & =\\beta_{i} \\sigma_{p}\\end{aligned}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c38981e8",
   "metadata": {},
   "source": [
    "其中$\\beta_{i}$表示证券$i$收益率相对于投资组合收益率的$\\beta$系数；"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7431fe50",
   "metadata": {},
   "source": [
    "定义了证券的边际风险贡献后，乘以其权重我们既可以得到风险贡献，"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d6f16331",
   "metadata": {},
   "source": [
    "$$\n",
    "\\mathrm{RC}_{\\mathrm{i}}=\\omega_{\\mathrm{i}} \\times M R C_{i}=\\omega_{\\mathrm{i}} \\frac{\\partial \\sigma_{\\mathrm{p}}}{\\partial \\omega_{i}}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1b1ee2f3",
   "metadata": {},
   "source": [
    "由 Risk Parity 的定义有，"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8ba1ca30",
   "metadata": {},
   "source": [
    "$$\n",
    "\\mathrm{RC}_{\\mathrm{i}}=\\mathrm{RC}_{\\mathrm{j}} \\\\\n",
    "\\Rightarrow  \\omega_{i} \\frac{\\partial \\sigma_{p}}{\\partial \\omega_{i}}=\\omega_{j} \\frac{\\partial \\sigma_{p}}{\\partial \\omega_{j}}, \\quad \\forall i, j \\\\\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "80657e31",
   "metadata": {},
   "source": [
    "因此，风险平价组合的目标函数为，"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8ad93a52",
   "metadata": {},
   "source": [
    "$$\n",
    "\\operatorname{Min} \\sum_{i=1}^{N} \\sum_{j=1}^{N}\\left(R C_{i}-R C_{j}\\right)^{2} \\\\\n",
    "\\Rightarrow \\quad \\omega_{i} \\propto \\frac{1}{\\beta_{i}}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6259e9e2",
   "metadata": {},
   "source": [
    "在 Risk Parity投资组合中，证券的权重和它相对于组合的 β 成反比；β 越高，其权重越低，从而有效的分散了风险，每个资产对组合的边际风险贡献相同。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ff61ee56",
   "metadata": {},
   "source": [
    "#### 6.3.6 均值方差优化"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "13959394",
   "metadata": {},
   "source": [
    "以上的介绍的最小方差、最大分散度、风险平价的优化方法，都还是集中在风险上。而我们常说，一个优秀的投资策略，往往是在给定风险水平下实现组合收益最大化，或者在给定收益水平实现组合风险最小化。稍微有一些金融相关背景的读者可能已经提前猜到了，最终我们绕不开的优化方法，也是组合优化问题中的老大哥，即Markowitz的均值方差优化。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "52ff4287",
   "metadata": {},
   "source": [
    "其目标函数为，"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a131fcc9",
   "metadata": {},
   "source": [
    "$$\n",
    "\\operatorname{Max} \\quad \\omega^{\\mathrm{T}} \\mu-\\frac{\\lambda}{2} \\omega^{\\prime} \\Sigma \\omega\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cf8ec1e0",
   "metadata": {},
   "source": [
    "理论上来讲，组合成分间存在无数个混搭方式，每种方式得到一个收益风险对，将所有结果集合在一起，就形成了可行域，如图所示。可行域中并不是所有点都是“好结果”，只有处于可行域上侧边缘的点才是最优值，即MVO的解，如图中A到D之间连线，这条线称为有效前沿。任何异于有效前沿的点，均能找到相同风险（收益）下收益（风险）更高（低）的组合。"
   ]
  },
  {
   "attachments": {
    "efficient%20frontier.png": {
     "image/png": "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"
    }
   },
   "cell_type": "markdown",
   "id": "a0f9dd35",
   "metadata": {},
   "source": [
    "![efficient%20frontier.png](attachment:efficient%20frontier.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "173902c8",
   "metadata": {},
   "source": [
    "图片来源：CQR"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "37ef9422",
   "metadata": {},
   "source": [
    "其中，图上的A点即前文讨论过的最小方差组合，位于有效前沿的最左端；而如果我们自无风险收益率起做一条射线，与有效前沿相切于B点，改点即为所有可行域中夏普比率最大的点，因此也被称为最大夏普组合。故也有另外一种常见的，最大化组合夏普比率，其目标函数为。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "725959ee",
   "metadata": {},
   "source": [
    "$$\n",
    "\\operatorname{Max} \\frac{\\omega^{\\prime} \\mu}{\\sqrt{\\omega^{\\prime} \\sum \\omega}}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "25ed947a",
   "metadata": {},
   "source": [
    "相比于前文介绍的各个优化方法，均值方差优化引入了更多的参数。在实践中，也面临着更多的问题。由于引入了更多的参数，尤其是对预期收益率的估计，会使得优化结果对参数的输入非常敏感，结果就是优化出的权重在时序上换手较快；容易输出极端大或极端小的权重，最终组合时常在个别证券上集中度过高；基于历史数据的均值方差组合，由于估计误差，在样本外甚至很难超越等权重组合。当然列举诸多问题，并非是说均值方差模型徒有虚名。恰恰相反，均值方差模型在组合优化领域是最常用也最经典的，正是因此，我们应该更深入的去了解经典模型背后的优劣，使其在投资实践中发挥最大的价值。事实上，针对这些问题后续的改进工作也一直没有停过，比较出名的例如Black-Litterman模型，又或是Bayes-Stein模型，由于篇幅所限，暂时就不在此提及，有兴趣的同学可以自行去深入了解一下。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "982bd46d",
   "metadata": {},
   "source": [
    "#### 6.3.7 常见约束"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "44381406",
   "metadata": {},
   "source": [
    "在实际投资中，往往除了最现实的资金限制外，还会有各种各样的限制。比较常见的限制有，"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ff2df341",
   "metadata": {},
   "source": [
    "**约束1：单资产权重的范围限制**："
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9ea1c0c9",
   "metadata": {},
   "source": [
    "$$\n",
    "\\omega_{lb} \\leq \\omega \\leq \\omega_{ub}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "16ead18b",
   "metadata": {},
   "source": [
    "**约束2：做空限制**"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "73c19d5f",
   "metadata": {},
   "source": [
    "在A股市场融券的成本较高，因此一般是默认存在做空限制"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d7823a81",
   "metadata": {},
   "source": [
    "$$\n",
    "\\begin{array}{l}\\omega^{T} \\mathbf{1}=1 \\\\ \\omega \\geq \\mathbf{0}\\end{array}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f9a08ac5",
   "metadata": {},
   "source": [
    "另外，常见的量化策略例如指数增强和中性对冲，往往都有明确对标的基准指数，由此衍生出相对基准指数内的成分股的一些限制，"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1f26a53e",
   "metadata": {},
   "source": [
    "**约束3：行业中性化**"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cfae0de4",
   "metadata": {},
   "source": [
    "$$\n",
    "\\left(\\omega-\\omega_{\\text {benchmark }}\\right)^{T} I_{\\text {industry } \\in D}=\\mathbf{0}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ce0e90ac",
   "metadata": {},
   "source": [
    "其中，$\\omega_{benchmark}$是基准指数内各成分股的权重向量，$I_{indusrty}$是代表行业的哑变量矩阵。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0c763390",
   "metadata": {},
   "source": [
    "**约束4：风险敞口限制**"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8048b598",
   "metadata": {},
   "source": [
    "$$\n",
    "\\left|\\left(\\omega-\\omega_{\\text {benchmark }}\\right)^{T}\\right| f \\leq f_{ub} \\in[0,1]\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "28d94371",
   "metadata": {},
   "source": [
    "其中，$\\omega_{benchmark}$是基准指数内各成分股的权重向量，$f$是风险因子暴露向量，$f_{ub}$是因子的风险敞口上限。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b0f63393",
   "metadata": {},
   "source": [
    "### 6.4 Python实现最佳仓位控制"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ae410bc2",
   "metadata": {},
   "source": [
    "在Python中我们可以使用scipy库构建组合优化模型，实现最佳仓位控制。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4b9583d2",
   "metadata": {},
   "source": [
    "首先，我们导入如下Python库"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "eba1c504",
   "metadata": {},
   "outputs": [],
   "source": [
    "import akshare as ak\n",
    "import datetime\n",
    "import warnings\n",
    "warnings.filterwarnings('ignore')\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "from scipy.optimize import minimize"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c946af0b",
   "metadata": {},
   "source": [
    "定义一些辅助函数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "a43ce224",
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_weights(df: pd.DataFrame, target='sharp', canshort=False) -> pd.Series:\n",
    "    '''\n",
    "    :param df: 资产日度涨跌幅矩阵\n",
    "    :param target: 优化目标 sharp→最大夏普比组合 rp→风险平价组合  var→最小风险组合\n",
    "    :param canshort: 是否可以做空\n",
    "    :return: 组合比率\n",
    "    '''\n",
    "    MeanReturn = df.mean().values  # 期望收益\n",
    "    Cov = df.cov()  # 协方差\n",
    "\n",
    "    # 定义优化函数、初始值、约束条件\n",
    "    # 负夏普比\n",
    "    def neg_sharp(w):\n",
    "        R = w @ MeanReturn\n",
    "        Var = w @ Cov @ w.T\n",
    "        sharp = R / Var ** 0.5\n",
    "        return -sharp * np.sqrt(240)\n",
    "\n",
    "    # 风险\n",
    "    def variance(w):\n",
    "        Var = w @ Cov @ w.T\n",
    "        return Var * 10000\n",
    "\n",
    "    def RiskParity(w):\n",
    "        weights = np.array(w)  # weights为一维数组\n",
    "        sigma = np.sqrt(np.dot(weights, np.dot(Cov, weights)))  # 获取组合标准差\n",
    "        # sigma = np.sqrt(weights@cov@weights)\n",
    "        MRC = np.dot(Cov, weights) / sigma  # MRC = Cov@weights/sigma\n",
    "        # MRC = np.dot(weights,cov)/sigma\n",
    "        TRC = weights * MRC\n",
    "        delta_TRC = [sum((i - TRC) ** 2) for i in TRC]\n",
    "        return sum(delta_TRC)\n",
    "\n",
    "    # 设置初始值\n",
    "    w0 = np.ones(df.shape[1]) / df.shape[1]\n",
    "    # 约束条件 w之和为1\n",
    "    cons = [{'type': 'eq', 'fun': lambda w: np.sum(w) - 1}]\n",
    "    bnds = tuple((0, 1) for w in w0)  # 做多的约束条件，如果可以做空，则不传入该条件\n",
    "\n",
    "    if target == 'sharp':\n",
    "        fc = neg_sharp\n",
    "    elif target == 'rp':\n",
    "        fc = RiskParity\n",
    "    elif target == 'var':\n",
    "        fc = variance\n",
    "\n",
    "    if canshort:\n",
    "        res = minimize(fc, w0, method='SLSQP', constraints=cons, options={'maxiter': 100})\n",
    "    else:\n",
    "        res = minimize(fc, w0, method='SLSQP', constraints=cons, options={'maxiter': 100}, bounds=bnds)\n",
    "\n",
    "    # if target == 'sharp':\n",
    "    #     print('最高夏普:', -res.fun)\n",
    "    # elif target == 'rp':\n",
    "    #     print('风险平价:', res.fun)\n",
    "    # elif target == 'var':\n",
    "    #     print('最低风险:', res.fun)\n",
    "\n",
    "    # print('最优比率:', res.x)\n",
    "    # print('年化收益:', ReturnYearly(res.x) * 100, \"%\")\n",
    "    weight = pd.Series(res.x, index=df.columns)\n",
    "    return weight"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b32cbe10",
   "metadata": {},
   "source": [
    "借助akshare我们下载股票的复权收盘价，计算日收益率"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "803cd2ad",
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_ret(code,start_date,end_date):\n",
    "    data = ak.stock_zh_a_hist(symbol=code, period=\"daily\", start_date=start_date, end_date=end_date, adjust=\"hfq\")\n",
    "    data.index = pd.to_datetime(data['日期'], format='%Y-%m-%d')  # 设置日期索引\n",
    "    close = data['收盘']  # 日收盘价\n",
    "    close.name = code\n",
    "    ret = close.pct_change() # 日收益率\n",
    "    return ret"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "b82c9f12",
   "metadata": {},
   "outputs": [],
   "source": [
    "ret_list = []\n",
    "for code in stock_codes:\n",
    "    ret = get_ret(code,start_date=start_date,end_date=end_date)\n",
    "    ret_list.append(ret)\n",
    "df_ret = pd.concat(ret_list,axis=1).dropna()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eb102311",
   "metadata": {},
   "source": [
    "数据和模型都准备好后，我们就可以在时间轴上滚动计算最优化模型的权重，注意不要使用未来数据。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "753b908b",
   "metadata": {},
   "outputs": [],
   "source": [
    "records = []\n",
    "for trade_date in df_ret.loc[index_stock_cons_weight_csindex_df['日期'].iat[0]:].index.to_list():\n",
    "    df_train = df_ret.loc[:trade_date].iloc[-1-240:-1]\n",
    "    df_test = df_ret.loc[trade_date]\n",
    "    StockSharpDf = get_weights(df_train, target='sharp')  # 最大夏普组合\n",
    "    StockRPDf = get_weights(df_train, target='rp')  # 风险平价组合\n",
    "    StockVarDf = get_weights(df_train, target='var')  # 最小风险组合\n",
    "    records.append([trade_date,\n",
    "                    (df_test.mul(StockSharpDf)).sum(),\n",
    "                    (df_test.mul(StockRPDf)).sum(),\n",
    "                    (df_test.mul(StockVarDf)).sum(),\n",
    "                    df_test.mean()])\n",
    "df_record = pd.DataFrame(records,columns=['日期','最大夏普组合','风险平价组合','最小风险组合','等权重组合'])\n",
    "df_record = df_record.set_index('日期')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8b43ced9",
   "metadata": {},
   "source": [
    "### 参考资料"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7b852a97",
   "metadata": {},
   "source": [
    "[如何分配资金？组合优化的是是非非](https://mp.weixin.qq.com/s/Xm6tmd4WQEDnMw7Dft8TPw)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "195595f0",
   "metadata": {},
   "source": [
    "[浅析资产配置的几种方法](https://mp.weixin.qq.com/s/0WBpcEkWaNTIJ8H-u_9-OA)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "67ebee76",
   "metadata": {},
   "source": [
    "[投资组合优化模型](https://mp.weixin.qq.com/s?__biz=MzAxNTc0Mjg0Mg==&mid=2653297069&idx=1&sn=0ce39862e32f523faf81a971caa357ad&scene=21#wechat_redirect)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fae37816",
   "metadata": {},
   "source": [
    "[【实战】寻找最优投资组合：马科维兹 | 风险平价 | 最大夏普](https://www.wolai.com/stupidccl/vUp4zkiqPf2axRJvLAFfaa)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7be91a82",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "af9db54c",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
